﻿using Bio.IO.Newick;
using Bio.Phylogenetics;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;


namespace Phylogeny.Dendogram
{
    public class Dendogram<T> : INotifyPropertyChanged
    {
        private TreeConfig _config = new TreeConfig();
        public TreeConfig Config
        {
            get
            {
                return _config;
            }
        }


        private List<string> _errors = new List<string>();
        public List<string> Errors
        {
            get
            {
                return _errors;
            }
        }

        private DendoNodes<T> _tree;
        public DendoNodes<T> Tree
        {
            get
            {
                return _tree;
            }
            private set
            {
                _tree = value;
                OnPropertyChanged("Tree");
            }
        }


        public void Prepare()
        {
            Tree = new DendoNodes<T>();
        }
        public bool FromNewickString(string tree)
        {
            bool ret = true;
            _errors.Clear();
            if (string.IsNullOrEmpty(tree))
            {
                ret = false;
                Tree = null;
            }
            else
            {
                using (NewickParser parser = new NewickParser())
                {
                    StringBuilder sb = new StringBuilder(tree);

                    Tree parsedTree = null;
                    try
                    {
                        parsedTree = parser.Parse(sb);
                    }
                    catch (Exception exc)
                    {
                        _errors.Add(exc.Message);
                        ret = false;
                    }


                    if (parsedTree != null)
                        BuildDendogram(parsedTree);
                    else
                    {
                        ret = false;
                        Tree = null;
                    }
                }
            }
            OnPropertyChanged("Errors");
            OnPropertyChanged("Tree");
            return ret;
        }

        private void BuildDendogram(Tree pyloTree)
        {

            DendoNode<T> treeRoot = new DendoNode<T>(Tree, _config, true);
            treeRoot.Distance = double.NaN;
            treeRoot.Title = pyloTree.Name;
            BuildSubtree(treeRoot, pyloTree.Root);
            ScaleTree(treeRoot, 500);
        }

        private void BuildSubtree(DendoNode<T> ret, Node root)
        {

            foreach (var item in root.Nodes)
            {
                DendoNode<T> node = new DendoNode<T>(ret.SubNodes, ret.Config);
                node.Distance = root.Children[item].Distance;
                node.Title = item.Name;
                if (item.IsLeaf)
                {
                    DendoNode<T> leafLabel = new DendoNode<T>(node.SubNodes, node.Config);
                    leafLabel.IsLeafLabel = true;
                    leafLabel.Title = node.Title;
                    leafLabel.Distance = double.NaN;
                }
                BuildSubtree(node, item);
            }
        }



        private void ScaleTree(DendoNode<T> treeRoot, double p)
        {
            var nodes = GetAllNodes(treeRoot);
            var maxDist = double.MinValue;
            foreach (var item in nodes)
            {

                if (!double.IsNaN(item.Distance) && item.Distance > 0)
                    maxDist = Math.Max(maxDist, item.Distance);
            }
            if (maxDist < p)
            {
                var factor = p / maxDist;
                _config.ScaleFactor = factor;
            }

        }

        internal List<DendoNodeBase> GetAllNodes(DendoNodeBase root)
        {
            List<DendoNodeBase> ret = new List<DendoNodeBase>();
            ret.Add(root);
            foreach (var item in root.GetSubNodes())
            {
                ret.AddRange(GetAllNodes(item));
            }
            return ret;
        }


        #region INotifyPropertyChanged Members

        protected void OnPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
